﻿using DotNetCommon;
using DotNetCommon.Accessors;
using DotNetCommon.Data;
using DotNetCommon.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace DBUtil.Builders;
public class CaseSegBuilder
{
    private readonly DBAccess db = null;

    private readonly List<(object when, object value, string[] prefixs, bool[] enum2Strings)> whens = [];

    private object elseValue = null;
    private string[] elsePrefixs = null;
    private bool[] elseEnum2Strings = null;

    private readonly LambdaExpression caseLambdaExpression;
    private readonly string caseColName = null;
    internal CaseSegBuilder(DBAccess db)
    {
        this.db = db;
    }

    internal CaseSegBuilder(DBAccess db, LambdaExpression lambdaExpression)
    {
        this.db = db;
        this.caseLambdaExpression = lambdaExpression;
    }
    internal CaseSegBuilder(DBAccess db, string colName)
    {
        this.db = db;
        this.caseColName = colName;
    }

    #region when
    /// <inheritdoc cref="DBAccess.CaseSeg(string)"/>
    public WhenSegBuilder WhenSeg(object constValue)
    {
        return new WhenSegBuilder(this, constValue);
    }

    public WhenSegBuilder WhenSeg<T>(Expression<Func<T, object>> expression, string prefix = null, bool? enum2String = null)
    {
        AssertUtil.NotNull(expression);
        return new WhenSegBuilder(this, expression, [prefix], enum2String == null ? [] : [enum2String.Value]);
    }
    public WhenSegBuilder WhenSeg<T, T2>(Expression<Func<T, T2, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        return new WhenSegBuilder(this, expression, prefixs, enum2Strings);
    }

    public WhenSegBuilder WhenSeg<T, T2, T3>(Expression<Func<T, T2, T3, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        return new WhenSegBuilder(this, expression, prefixs, enum2Strings);
    }
    public WhenSegBuilder WhenSeg<T, T2, T3, T4>(Expression<Func<T, T2, T3, T4, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        return new WhenSegBuilder(this, expression, prefixs, enum2Strings);
    }
    public WhenSegBuilder WhenSeg<T, T2, T3, T4, T5>(Expression<Func<T, T2, T3, T4, T5, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        return new WhenSegBuilder(this, expression, prefixs, enum2Strings);
    }
    public WhenSegBuilder WhenSeg<T, T2, T3, T4, T5, T6>(Expression<Func<T, T2, T3, T4, T5, T6, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        return new WhenSegBuilder(this, expression, prefixs, enum2Strings);
    }
    #endregion

    #region else
    public ElseSegBuilder ElseSeg(object obj)
    {
        elseValue = obj;
        return new ElseSegBuilder(this);
    }
    public ElseSegBuilder ElseSeg<T>(Expression<Func<T, object>> expression, string prefix = null, bool? enum2String = null)
    {
        AssertUtil.NotNull(expression);
        elseValue = expression;
        elsePrefixs = [prefix];
        elseEnum2Strings = enum2String == null ? [] : [enum2String.Value];
        return new ElseSegBuilder(this);
    }
    public ElseSegBuilder ElseSeg<T, T2>(Expression<Func<T, T2, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        elseValue = expression;
        elsePrefixs = prefixs;
        elseEnum2Strings = enum2Strings;
        return new ElseSegBuilder(this);
    }

    public ElseSegBuilder ElseSeg<T, T2, T3>(Expression<Func<T, T2, T3, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        elseValue = expression;
        elsePrefixs = prefixs;
        elseEnum2Strings = enum2Strings;
        return new ElseSegBuilder(this);
    }
    public ElseSegBuilder ElseSeg<T, T2, T3, T4>(Expression<Func<T, T2, T3, T4, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        elseValue = expression;
        elsePrefixs = prefixs;
        elseEnum2Strings = enum2Strings;
        return new ElseSegBuilder(this);
    }
    public ElseSegBuilder ElseSeg<T, T2, T3, T4, T5>(Expression<Func<T, T2, T3, T4, T5, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        elseValue = expression;
        elsePrefixs = prefixs;
        elseEnum2Strings = enum2Strings;
        return new ElseSegBuilder(this);
    }
    public ElseSegBuilder ElseSeg<T, T2, T3, T4, T5, T6>(Expression<Func<T, T2, T3, T4, T5, T6, object>> expression, string[] prefixs = null, bool[] enum2Strings = null)
    {
        AssertUtil.NotNull(expression);
        elseValue = expression;
        elsePrefixs = prefixs;
        elseEnum2Strings = enum2Strings;
        return new ElseSegBuilder(this);
    }
    #endregion

    #region endas
    public string EndAs(string colName)
    {
        AssertUtil.NotNullOrWhiteSpace(colName);
        return EndAsInternal(colName);
    }
    public string EndAs<TEntity>(Expression<Func<TEntity, object>> expression) where TEntity : class, new()
    {
        AssertUtil.NotNull(expression);
        var prop = ExpressionHelper.GetAccessNames(expression).FirstOrDefault();
        if (prop == null) throw new Exception("必须指定正确的列, 如: .EndAs<Person>(i => i.Name)");
        return EndAs(prop);
    }
    public string End() => EndAsInternal();
    private string ConvertSql(object obj, string[] prefixs = null, bool[] enum2Strings = null)
    {
        if (obj is LambdaExpression lambda)
        {
            return db.ParseToSqlInternal(lambda, prefixs, enum2Strings);
        }
        return db.ConvertToSqlSeg(obj).UnWrap();
    }
    private string EndAsInternal(string colName = null)
    {
        var sb = new StringBuilder();
        //case
        sb.Append("case ");
        if (caseLambdaExpression != null)
        {
            var col = ConvertSql(caseLambdaExpression);
            sb.Append(col);
        }
        else if (caseColName.IsNotNullOrWhiteSpace())
        {
            sb.Append(caseColName);
        }

        //when then
        foreach (var when in whens)
        {
            sb.AppendLine().Append("  when ");
            var col = ConvertSql(when.when, when.prefixs, when.enum2Strings);
            sb.Append(col);
            sb.Append(" then ");
            var val = ConvertSql(when.value, when.prefixs, when.enum2Strings);
            sb.Append(val);
        }
        //else
        if (elseValue != null)
        {
            sb.AppendLine().Append("  else ");
            var col = ConvertSql(elseValue, elsePrefixs, elseEnum2Strings);
            sb.Append(col);
        }
        //endas
        if (colName.IsNotNullOrWhiteSpace())
        {
            sb.Append(" end ").Append(colName);
        }
        else
        {
            sb.Append(" end");
        }
        var sql = sb.ToString();
        sb.Clear();
        return sql;
    }
    #endregion

    private void AddWhenThen(object obj, object obj2, string[] prefixs = null, bool[] enum2Strings = null)
    {
        whens.Add((obj, obj2, prefixs, enum2Strings));
    }

    public class WhenSegBuilder
    {
        private readonly CaseSegBuilder caseSegBuilder;
        private readonly object when;
        private readonly string[] prefixs;
        private readonly bool[] enum2Strings;

        internal WhenSegBuilder(CaseSegBuilder caseSegBuilder, object obj)
        {
            this.caseSegBuilder = caseSegBuilder;
            this.when = obj;
        }

        internal WhenSegBuilder(CaseSegBuilder caseSegBuilder, LambdaExpression lambdaExpression, string[] prefixes, bool[] enum2Strings)
        {
            this.caseSegBuilder = caseSegBuilder;
            this.when = lambdaExpression;
            this.prefixs = prefixes;
            this.enum2Strings = enum2Strings;
        }

        #region then
        public CaseSegBuilder Then(object obj)
        {
            caseSegBuilder.AddWhenThen(when, obj, prefixs, enum2Strings);
            return caseSegBuilder;
        }

        public CaseSegBuilder Then<T>(Expression<Func<T, object>> expression) where T : class, new()
        {
            AssertUtil.NotNull(expression);
            caseSegBuilder.AddWhenThen(when, expression, prefixs, enum2Strings);
            return caseSegBuilder;
        }
        public CaseSegBuilder CaseSeg<T, T2>(Expression<Func<T, T2, object>> expression)
            where T : class, new()
            where T2 : class, new()
        {
            AssertUtil.NotNull(expression);
            caseSegBuilder.AddWhenThen(when, expression, prefixs, enum2Strings);
            return caseSegBuilder;
        }

        public CaseSegBuilder CaseSeg<T, T2, T3>(Expression<Func<T, T2, T3, object>> expression)
            where T : class, new()
            where T2 : class, new()
            where T3 : class, new()
        {
            AssertUtil.NotNull(expression);
            caseSegBuilder.AddWhenThen(when, expression, prefixs, enum2Strings);
            return caseSegBuilder;
        }
        public CaseSegBuilder CaseSeg<T, T2, T3, T4>(Expression<Func<T, T2, T3, T4, object>> expression)
            where T : class, new()
            where T2 : class, new()
            where T3 : class, new()
            where T4 : class, new()
        {
            AssertUtil.NotNull(expression);
            caseSegBuilder.AddWhenThen(when, expression, prefixs, enum2Strings);
            return caseSegBuilder;
        }
        public CaseSegBuilder CaseSeg<T, T2, T3, T4, T5>(Expression<Func<T, T2, T3, T4, T5, object>> expression)
            where T : class, new()
            where T2 : class, new()
            where T3 : class, new()
            where T4 : class, new()
            where T5 : class, new()
        {
            AssertUtil.NotNull(expression);
            caseSegBuilder.AddWhenThen(when, expression, prefixs, enum2Strings);
            return caseSegBuilder;
        }
        public CaseSegBuilder CaseSeg<T, T2, T3, T4, T5, T6>(Expression<Func<T, T2, T3, T4, T5, T6, object>> expression)
            where T : class, new()
            where T2 : class, new()
            where T3 : class, new()
            where T4 : class, new()
            where T5 : class, new()
            where T6 : class, new()
        {
            AssertUtil.NotNull(expression);
            caseSegBuilder.AddWhenThen(when, expression, prefixs, enum2Strings);
            return caseSegBuilder;
        }
        #endregion
    }

    public class ElseSegBuilder
    {
        private readonly CaseSegBuilder caseSegBuilder;
        public ElseSegBuilder(CaseSegBuilder caseSegBuilder)
        {
            this.caseSegBuilder = caseSegBuilder;
        }
        public string EndAs(string colName) => caseSegBuilder.EndAs(colName);
        public string EndAs<TEntity>(Expression<Func<TEntity, object>> expression) where TEntity : class, new() => caseSegBuilder.EndAs(expression);
        public string End() => caseSegBuilder.EndAsInternal();
    }
}

public class CaseListSegBuilder<T> where T : class, new()
{
    private readonly DBAccess db = null;
    private readonly IEnumerable<T> list;
    private readonly LambdaExpression caseExp;
    private readonly LambdaExpression thenExp;

    internal CaseListSegBuilder(DBAccess db, IEnumerable<T> list, LambdaExpression caseExp, LambdaExpression thenExp)
    {
        this.db = db;
        this.list = list;
        this.caseExp = caseExp;
        this.thenExp = thenExp;
    }

    private const string errMsg = "db.CaseListSeg()使用错误, 正确示例: db.CaseListSeg(list,i=>i.Id,i=>i.Name) 这将生成如: case id when 1 then 'name1' when 2 then 'name2' end\"";
    private string ToStringInternal(string colName = null)
    {
        var caseProps = ExpressionHelper.GetInitOrReturnPropNames(caseExp);
        if (caseProps.IsNullOrEmpty() || caseProps.Count > 1) throw new Exception(errMsg);
        var thenProps = ExpressionHelper.GetInitOrReturnPropNames(thenExp);
        if (thenProps.IsNullOrEmpty() || thenProps.Count > 1) throw new Exception(errMsg);
        var sb = new StringBuilder();
        //case
        var caseProp = caseProps.First();
        sb.Append($"case {db.GetEntityInfoInternal<T>().Props.First(i => i.PropNamePure == caseProps.First()).SelectValueFunc(null)}\r\n");
        var thenProp = thenProps.First();
        var accessor = Accessor.Build<T>();
        foreach (var item in list)
        {
            sb.Append($"  when {db.ConvertToSqlSeg(accessor[item, caseProp]).Data} then {db.ConvertToSqlSeg(accessor[item, thenProp]).Data}\r\n");
        }
        sb.Remove(sb.Length - 2, 2);
        sb.Append($" end");
        if (colName.IsNotNullOrWhiteSpace()) sb.Append(' ').Append(colName);
        var sql = sb.ToString();
        sb.Clear();
        return sql;
    }

    public string EndAs(string colName)
    {
        return ToStringInternal(colName);
    }

    public string End() => ToStringInternal();

    public override string ToString() => ToStringInternal();
}
